//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2012 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------
#include <gmock/gmock.h>

#include <sqbind/sqbBind.h>
#include <sqbind/sqbClassTypeTag.h>

#include "fixtures/TypeFixture.h"
//----------------------------------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------------------------------
class BaseWithUnboundVariable
{
public:

  UnboundClass m_unboundVariable;
};

//----------------------------------------------------------------------------------------------------------------------
template<typename TypeParam>
class BaseWithVariable
{
public:

  TypeParam m_baseVariable;

  BaseWithVariable(const TypeParam &baseVariable) : m_baseVariable(baseVariable) {}
};

//----------------------------------------------------------------------------------------------------------------------
template<typename TypeParam>
class DerivedWithVariableNoOffset : public BaseWithVariable<TypeParam>
{
public:
  uint8_t   m_pad[1];
  TypeParam m_derivedVariable;

  DerivedWithVariableNoOffset(const TypeParam &baseVariable, const TypeParam &derivedVariable)
  : BaseWithVariable(baseVariable),
    m_derivedVariable(derivedVariable)
  {
  }
};

//----------------------------------------------------------------------------------------------------------------------
template<typename TypeParam>
class DerivedWithVariableWithOffset : public BaseWithVariable<TypeParam>
{
public:
  uint8_t   m_pad[1];
  TypeParam m_derivedVariable;

  DerivedWithVariableWithOffset(const TypeParam &baseVariable, const TypeParam &derivedVariable)
  : BaseWithVariable(baseVariable),
    m_derivedVariable(derivedVariable)
  {
  }

  virtual ~DerivedWithVariableWithOffset() {}
};

//----------------------------------------------------------------------------------------------------------------------
template<typename TypeParam>
class BindVariableTest : public BaseTypeFixture<TypeParam>
{
public:
  typedef sqb::TypeInfo<TypeParam>                  TypeInfo;

  typedef BaseWithVariable<TypeParam>               BaseWithVariable;
  typedef DerivedWithVariableNoOffset<TypeParam>    DerivedWithVariableNoOffset;
  typedef DerivedWithVariableWithOffset<TypeParam>  DerivedWithVariableWithOffset;

  virtual void SetUp() override
  {
    BaseTypeFixture::SetUp();

    HSQOBJECT class_object;

    // setup the BaseWithVariable class
    //
    EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));
    EXPECT_SQ_SUCCEEDED(m_vm, sq_settypetag(m_vm, -1, sqb::ClassTypeTag<BaseWithVariable>::Get()));
    sq_getstackobj(m_vm, -1, &class_object);
    sqb::ClassTypeTag<BaseWithVariable>::Get()->SetClassObject(m_vm, class_object);

    // setup the DerivedWithVariableNoOffset class
    //
    sqb::ClassTypeTag<DerivedWithVariableNoOffset>::Get()->SetBaseClass(static_cast<BaseWithVariable *>(nullptr));
    sq_push(m_vm, -1); // push Base for the call to sq_newclass
    EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQTrue));
    EXPECT_SQ_SUCCEEDED(m_vm, sq_settypetag(m_vm, -1, sqb::ClassTypeTag<DerivedWithVariableNoOffset>::Get()));
    sq_getstackobj(m_vm, -1, &class_object);
    sqb::ClassTypeTag<DerivedWithVariableNoOffset>::Get()->SetClassObject(m_vm, class_object);
    sq_poptop(m_vm);

    // setup the DerivedWithVariableWithOffset class
    //
    sqb::ClassTypeTag<DerivedWithVariableWithOffset>::Get()->SetBaseClass(static_cast<BaseWithVariable *>(nullptr));
    sq_push(m_vm, -1); // push Base for the call to sq_newclass
    EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQTrue));
    EXPECT_SQ_SUCCEEDED(m_vm, sq_settypetag(m_vm, -1, sqb::ClassTypeTag<DerivedWithVariableWithOffset>::Get()));
    sq_getstackobj(m_vm, -1, &class_object);
    sqb::ClassTypeTag<DerivedWithVariableWithOffset>::Get()->SetClassObject(m_vm, class_object);
    sq_poptop(m_vm);

    // pop BaseWithVariable from stack
    //
    sq_poptop(m_vm);
  }
};

TYPED_TEST_CASE(BindVariableTest, AllTypes);

SQBIND_DECLARE_TYPEINFO(BaseWithVariable<bool>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<bool>, BaseWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<bool>, BaseWithVariableWithOffset);

SQBIND_DECLARE_TYPEINFO(BaseWithVariable<char>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<char>, DerivedWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<char>, DerivedWithVariableWithOffset);

SQBIND_DECLARE_TYPEINFO(BaseWithVariable<int8_t>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<int8_t>, DerivedWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<int8_t>, DerivedWithVariableWithOffset);

SQBIND_DECLARE_TYPEINFO(BaseWithVariable<uint8_t>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<uint8_t>, DerivedWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<uint8_t>, DerivedWithVariableWithOffset);

SQBIND_DECLARE_TYPEINFO(BaseWithVariable<int16_t>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<int16_t>, DerivedWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<int16_t>, DerivedWithVariableWithOffset);

SQBIND_DECLARE_TYPEINFO(BaseWithVariable<uint16_t>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<uint16_t>, DerivedWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<uint16_t>, DerivedWithVariableWithOffset);

SQBIND_DECLARE_TYPEINFO(BaseWithVariable<int32_t>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<int32_t>, DerivedWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<int32_t>, DerivedWithVariableWithOffset);

SQBIND_DECLARE_TYPEINFO(BaseWithVariable<uint32_t>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<uint32_t>, DerivedWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<uint32_t>, DerivedWithVariableWithOffset);

SQBIND_DECLARE_TYPEINFO(BaseWithVariable<float>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<float>, DerivedWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<float>, DerivedWithVariableWithOffset);

SQBIND_DECLARE_TYPEINFO(BaseWithVariable<const SQChar *>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<const SQChar *>, DerivedWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<const SQChar *>, DerivedWithVariableWithOffset);

SQBIND_DECLARE_TYPEINFO(BaseWithVariable<SQUserPointer>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<SQUserPointer>, DerivedWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<SQUserPointer>, DerivedWithVariableWithOffset);

SQBIND_DECLARE_TYPEINFO(BaseWithVariable<BoundClass>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<BoundClass>, DerivedWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<BoundClass>, DerivedWithVariableWithOffset);

SQBIND_DECLARE_TYPEINFO(BaseWithVariable<TestEnum>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<TestEnum>, DerivedWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<TestEnum>, DerivedWithVariableWithOffset);

#if defined(_SQ64)
SQBIND_DECLARE_TYPEINFO(BaseWithVariable<int64_t>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<int64_t>, DerivedWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<int64_t>, DerivedWithVariableWithOffset);

SQBIND_DECLARE_TYPEINFO(BaseWithVariable<uint64_t>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<uint64_t>, DerivedWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<uint64_t>, DerivedWithVariableWithOffset);
#endif // defined(_SQ64)

#if defined(SQUSEDOUBLE)
SQBIND_DECLARE_TYPEINFO(BaseWithVariable<double>, BaseWithVariable);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableNoOffset<double>, DerivedWithVariableNoOffset);
SQBIND_DECLARE_TYPEINFO(DerivedWithVariableWithOffset<double>, DerivedWithVariableWithOffset);
#endif // defined(SQUSEDOUBLE)

typedef SquirrelFixture BindVariableFailTest;

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindVariableFailTest, TestBindVariableInvalidParameters)
{
  int32_t value;
  EXPECT_FALSE(sqb::Bind::BindVariable(m_vm, -1, &value, _SC("value")));

  // push the root table
  //
  sq_pushroottable(m_vm);

  EXPECT_FALSE(sqb::Bind::BindVariable(m_vm, -1, static_cast<int32_t *>(nullptr), _SC("value")));
  EXPECT_FALSE(sqb::Bind::BindVariable(m_vm, -1, &value, nullptr));
  EXPECT_FALSE(sqb::Bind::BindVariable(m_vm, -1, &value, _SC("")));

  // pop the root table
  //
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindVariableFailTest, TestBindInstanceVariableInvalidParameters)
{
  EXPECT_FALSE(sqb::Bind::BindInstanceVariable(m_vm, -1, &BaseWithVariable<int32_t>::m_baseVariable, _SC("value")));

  sq_newclass(m_vm, SQFalse);
  EXPECT_FALSE(sqb::Bind::BindInstanceVariable(m_vm, -1, static_cast<int32_t BaseWithVariable<int32_t>:: *>(nullptr), _SC("value")));
  EXPECT_FALSE(sqb::Bind::BindInstanceVariable(m_vm, -1, &BaseWithVariable<int32_t>::m_baseVariable, nullptr));
  EXPECT_FALSE(sqb::Bind::BindInstanceVariable(m_vm, -1, &BaseWithVariable<int32_t>::m_baseVariable, _SC("")));
  EXPECT_FALSE(sqb::Bind::BindInstanceVariable(m_vm, -1, &BaseWithVariable<UnboundClass>::m_baseVariable, _SC("value")));
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindVariableFailTest, TestBindVariableUnboundType)
{
  // push the root table
  //
  sq_pushroottable(m_vm);

  UnboundClass value;
  EXPECT_FALSE(sqb::Bind::BindVariable(m_vm, -1, &value, _SC("value")));

  // pop the root table
  //
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindVariableFailTest, TestBindClassFailCreation)
{
  // push the root table
  //
  sq_pushroottable(m_vm);

  // check getting a class instance of an unbound type fails
  //
  BoundClass value(10);
  EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, -1, &value, _SC("value")));
  CompileAndFailCall(_SC("return value"));
  CheckErrorString(_SC("error getting variable 'value' ; check variable class type 'BoundClass' has been bound correctly"));

  // create a valid instance of BoundClass 'other' to use to try and set value
  //
  BoundClass expected(11);
  EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_settypetag(m_vm, -1, sqb::ClassTypeTag<BoundClass>::Get()));

  sq_pushstring(m_vm, _SC("expected"), -1);
  EXPECT_SQ_SUCCEEDED(m_vm, sq_createinstance(m_vm, -2));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_setinstanceup(m_vm, -1, &expected));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -4));

  // check setting a class instance of an unbound type fails
  //
  CompileAndFailCall(_SC("value = expected"));
  CheckErrorString(_SC("error setting variable 'value' ; check variable class type 'BoundClass' has been bound correctly"));

  // pop the root table and the class
  //
  sq_pop(m_vm, 2);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindVariableFailTest, TestBindClassSetWrongInstanceType)
{
  // push the root table
  //
  sq_pushroottable(m_vm);

  // setup the BoundClass class
  //
  EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_settypetag(m_vm, -1, sqb::ClassTypeTag<BoundClass>::Get()));
  HSQOBJECT class_object;
  sq_getstackobj(m_vm, -1, &class_object);
  sqb::ClassTypeTag<BoundClass>::Get()->SetClassObject(m_vm, class_object);
  sq_poptop(m_vm);

  // check getting a class instance of an unbound type fails
  //
  BoundClass value(10);
  EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, -1, &value, _SC("value")));

  // create another class with no typetag and test set fails
  //
  BoundClass expected(11);
  EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));

  sq_pushstring(m_vm, _SC("expected"), -1);
  EXPECT_SQ_SUCCEEDED(m_vm, sq_createinstance(m_vm, -2));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_setinstanceup(m_vm, -1, &expected));
  EXPECT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -4));

  // check setting a class instance of an unbound type fails
  //
  CompileAndFailCall(_SC("value = expected"));
  CheckErrorString(_SC("error setting variable 'value' ; expected 'BoundClass' got 'instance'"));

  // pop the root table and the class
  //
  sq_pop(m_vm, 2);
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindVariableFailTest, TestBindInvalidTypeInfoClass)
{
  // push the root table
  //
  sq_pushroottable(m_vm);

  // bind the class with invalid type info
  //
  InvalidTypeInfoClass instance;
  EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, -1, &instance, _SC("instance")));

  // check getting a class instance of a type with invalid type info fails
  //
  CompileAndFailCall(_SC("return instance"));
  CheckErrorString(_SC("error getting variable 'instance' ; unknown variable type id '%d'"), sqb::TypeInfo<InvalidTypeInfoClass>::kTypeID);

  // check setting a class instance of a type with invalid type info fails
  //
  CompileAndFailCall(_SC("instance = 10"));
  CheckErrorString(_SC("error setting variable 'instance' ; unknown variable type id '%d'"), sqb::TypeInfo<InvalidTypeInfoClass>::kTypeID);

  // pop the root table
  //
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TYPED_TEST(BindVariableTest, TestBindVariableGet)
{
  SCOPED_TRACE("BindVariableGet");

  // push the root table
  //
  sq_pushroottable(m_vm);

  // bind with relative index
  //
  TypeParam value = GetRandomValue();
  EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, -1, &value, _SC("value")));
  EXPECT_TYPE_PARAM_EQ(value, CompileAndCallReturnResult(_SC("return value")));

  // check index not found works
  //
  CompileAndFailCall(_SC("return invalid"));
  CheckErrorString(_SC("the index 'invalid' does not exist"));

  // pop the root table
  //
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TYPED_TEST(BindVariableTest, TestBindInstanceVariableGet)
{
  SCOPED_TRACE("BindInstanceVariableGet");

  // push the root table
  //
  sq_pushroottable(m_vm);

  // check base class
  //
  {
    HSQOBJECT baseClass = sqb::ClassTypeTag<BaseWithVariable>::Get()->GetClassObject(m_vm);
    sq_pushobject(m_vm, baseClass);
    EXPECT_TRUE(sqb::Bind::BindInstanceVariable(m_vm, -1, &BaseWithVariable::m_baseVariable, _SC("m_baseVariable")));

    TypeParam staticValue = GetRandomValue();
    EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, -1, &staticValue, _SC("m_staticVariable")));
    sq_poptop(m_vm);

    TypeParam expected = GetRandomValue();
    BaseWithVariable baseInstance(expected);
    EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, -1, &baseInstance, _SC("baseInstance")));
    EXPECT_TYPE_PARAM_EQ(expected, CompileAndCallReturnResult(_SC("return baseInstance.m_baseVariable")));
    EXPECT_TYPE_PARAM_EQ(staticValue, CompileAndCallReturnResult(_SC("return baseInstance.m_staticVariable")));

    // check index not found works
    //
    CompileAndFailCall(_SC("return baseInstance.invalid"));
    CheckErrorString(_SC("the index 'invalid' does not exist"));
  }

  // check derived class with no offset
  //
  {
    HSQOBJECT derivedNoOffsetClass = sqb::ClassTypeTag<DerivedWithVariableNoOffset>::Get()->GetClassObject(m_vm);
    sq_pushobject(m_vm, derivedNoOffsetClass);
    sqb::Bind::BindInstanceVariable(m_vm, -1, &DerivedWithVariableNoOffset::m_derivedVariable, _SC("m_derivedVariable"));
    sq_poptop(m_vm);

    TypeParam expectedBase = GetRandomValue();
    TypeParam expectedDerived = GetRandomValue();

    DerivedWithVariableNoOffset derivedNoOffsetInstance(expectedBase, expectedDerived);
    EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, -1, &derivedNoOffsetInstance, _SC("derivedNoOffsetInstance")));
    EXPECT_TYPE_PARAM_EQ(expectedBase, CompileAndCallReturnResult(_SC("return derivedNoOffsetInstance.m_baseVariable")));
    EXPECT_TYPE_PARAM_EQ(expectedDerived, CompileAndCallReturnResult(_SC("return derivedNoOffsetInstance.m_derivedVariable")));
  }

  // check derived class with an offset
  //
  {
    HSQOBJECT derivedWithOffsetClass = sqb::ClassTypeTag<DerivedWithVariableWithOffset>::Get()->GetClassObject(m_vm);
    sq_pushobject(m_vm, derivedWithOffsetClass);
    EXPECT_TRUE(sqb::Bind::BindInstanceVariable(m_vm, -1, &DerivedWithVariableWithOffset::m_derivedVariable, _SC("m_derivedVariable")));
    sq_poptop(m_vm);

    TypeParam expectedBase = GetRandomValue();
    TypeParam expectedDerived = GetRandomValue();

    DerivedWithVariableWithOffset derivedWithOffsetInstance(expectedBase, expectedDerived);
    EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, -1, &derivedWithOffsetInstance, _SC("derivedWithOffsetInstance")));
    EXPECT_TYPE_PARAM_EQ(expectedBase, CompileAndCallReturnResult(_SC("return derivedWithOffsetInstance.m_baseVariable")));
    EXPECT_TYPE_PARAM_EQ(expectedDerived, CompileAndCallReturnResult(_SC("return derivedWithOffsetInstance.m_derivedVariable")));
  }

  // pop the root table
  //
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TYPED_TEST(BindVariableTest, TestBindVariableSet)
{
  SCOPED_TRACE("BindVariableSet");

  // push the root table
  //
  sq_pushroottable(m_vm);

  // bind with absolute index
  //
  TypeParam actual = GetRandomValue();
  TypeParam expected = GetRandomValue();
  EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, 1, &actual, _SC("actual")));
  EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, 1, &expected, _SC("expected")));
  CompileAndFailCall(_SC("actual = function() { }"));
  CheckErrorString(_SC("error setting variable 'actual' ; expected '%s' got 'function'"), TypeInfo().m_typeName);

  EXPECT_NO_FATAL_FAILURE(CompileAndSucceedCall(_SC("actual = expected")));
  EXPECT_TYPE_PARAM_EQ(actual, expected);

  // check index not found works
  //
  CompileAndFailCall(_SC("invalid = expected"));
  CheckErrorString(_SC("the index 'invalid' does not exist"));

  // pop the root table
  //
  sq_poptop(m_vm);
}

//----------------------------------------------------------------------------------------------------------------------
TYPED_TEST(BindVariableTest, TestBindInstanceVariableSet)
{
  SCOPED_TRACE("BindInstanceVariableSet");

  // check base class
  //
  {
    HSQOBJECT baseClass = sqb::ClassTypeTag<BaseWithVariable>::Get()->GetClassObject(m_vm);
    sq_pushobject(m_vm, baseClass);
    EXPECT_TRUE(sqb::Bind::BindInstanceVariable(m_vm, 1, &BaseWithVariable::m_baseVariable, _SC("m_baseVariable")));

    TypeParam actualStaticValue = GetRandomValue();
    EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, -1, &actualStaticValue, _SC("m_staticVariable")));
    sq_poptop(m_vm);

    TypeParam actualBase = GetRandomValue();

    sq_pushroottable(m_vm);

    TypeParam expected = GetRandomValue();
    EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, 1, &expected, _SC("expected")));

    BaseWithVariable actual(actualBase);
    EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, 1, &actual, _SC("actual")));
    CompileAndFailCall(_SC("actual.m_baseVariable = function() { }"));
    EXPECT_TYPE_PARAM_EQ(actualBase, actual.m_baseVariable);
    CheckErrorString(_SC("error setting variable 'm_baseVariable' ; expected '%s' got 'function'"), TypeInfo().m_typeName);

    CompileAndSucceedCall(_SC("actual.m_baseVariable = expected"));
    EXPECT_TYPE_PARAM_EQ(expected, actual.m_baseVariable);

    CompileAndSucceedCall(_SC("actual.m_staticVariable = expected"));
    EXPECT_TYPE_PARAM_EQ(expected, actualStaticValue);

    // check index not found works
    //
    CompileAndFailCall(_SC("actual.invalid = expected"));
    CheckErrorString(_SC("the index 'invalid' does not exist"));

    sq_poptop(m_vm);
  }

  // check derived class with no offset
  //
  {
    HSQOBJECT derivedNoOffsetClass = sqb::ClassTypeTag<DerivedWithVariableNoOffset>::Get()->GetClassObject(m_vm);
    sq_pushobject(m_vm, derivedNoOffsetClass);
    EXPECT_TRUE(sqb::Bind::BindInstanceVariable(m_vm, 1, &DerivedWithVariableNoOffset::m_derivedVariable, _SC("m_derivedVariable")));
    sq_poptop(m_vm);

    TypeParam expectedBase = GetRandomValue();
    TypeParam expectedDerived = GetRandomValue();
    TypeParam actualBase = GetRandomValue();
    TypeParam actualDerived = GetRandomValue();

    sq_pushroottable(m_vm);
    DerivedWithVariableNoOffset expected(expectedBase, expectedDerived);
    DerivedWithVariableNoOffset actual(actualBase, actualDerived);
    EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, 1, &expected, _SC("expected")));
    EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, 1, &actual, _SC("actual")));
    CompileAndFailCall(_SC("actual.m_baseVariable = function() { }"));
    EXPECT_TYPE_PARAM_EQ(actualBase, actual.m_baseVariable);
    CheckErrorString(_SC("error setting variable 'm_baseVariable' ; expected '%s' got 'function'"), TypeInfo().m_typeName);

    CompileAndFailCall(_SC("actual.m_derivedVariable = function() { }"));
    EXPECT_TYPE_PARAM_EQ(actualDerived, actual.m_derivedVariable);
    CheckErrorString(_SC("error setting variable 'm_derivedVariable' ; expected '%s' got 'function'"), TypeInfo().m_typeName);

    CompileAndSucceedCall(_SC("actual.m_baseVariable = expected.m_baseVariable"));
    EXPECT_TYPE_PARAM_EQ(expected.m_baseVariable, actual.m_baseVariable);
    CompileAndSucceedCall(_SC("actual.m_derivedVariable = expected.m_derivedVariable"));
    EXPECT_TYPE_PARAM_EQ(expected.m_derivedVariable, actual.m_derivedVariable);
    sq_poptop(m_vm);
  }

  // check derived class with with offset
  //
  {
    HSQOBJECT derivedNoOffsetClass = sqb::ClassTypeTag<DerivedWithVariableWithOffset>::Get()->GetClassObject(m_vm);
    sq_pushobject(m_vm, derivedNoOffsetClass);
    EXPECT_TRUE(sqb::Bind::BindInstanceVariable(m_vm, 1, &DerivedWithVariableWithOffset::m_derivedVariable, _SC("m_derivedVariable")));
    sq_poptop(m_vm);

    TypeParam expectedBase = GetRandomValue();
    TypeParam expectedDerived = GetRandomValue();
    TypeParam actualBase = GetRandomValue();
    TypeParam actualDerived = GetRandomValue();

    sq_pushroottable(m_vm);
    DerivedWithVariableWithOffset expected(expectedBase, expectedDerived);
    DerivedWithVariableWithOffset actual(actualBase, actualDerived);
    EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, 1, &expected, _SC("expected")));
    EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, 1, &actual, _SC("actual")));
    CompileAndFailCall(_SC("actual.m_baseVariable = function() { }"));
    EXPECT_TYPE_PARAM_EQ(actualBase, actual.m_baseVariable);
    CheckErrorString(_SC("error setting variable 'm_baseVariable' ; expected '%s' got 'function'"), TypeInfo().m_typeName);

    CompileAndFailCall(_SC("actual.m_derivedVariable = function() { }"));
    EXPECT_TYPE_PARAM_EQ(actualDerived, actual.m_derivedVariable);
    CheckErrorString(_SC("error setting variable 'm_derivedVariable' ; expected '%s' got 'function'"), TypeInfo().m_typeName);

    CompileAndSucceedCall(_SC("actual.m_baseVariable = expected.m_baseVariable"));
    EXPECT_TYPE_PARAM_EQ(expected.m_baseVariable, actual.m_baseVariable);
    CompileAndSucceedCall(_SC("actual.m_derivedVariable = expected.m_derivedVariable"));
    EXPECT_TYPE_PARAM_EQ(expected.m_derivedVariable, actual.m_derivedVariable);
    sq_poptop(m_vm);
  }
}
